package com.hzw.saas.web.app.aspect;

import javax.annotation.Resource;

import com.hzw.saas.api.property.IPropertyService;
import com.hzw.saas.api.property.enmus.SysDictKeyEnum;
import com.hzw.saas.api.property.enmus.SysDictModuleEnum;
import com.hzw.saas.common.config.exception.DefaultSaasException;
import com.hzw.saas.common.util.SpringELUtils;
import cn.hutool.core.util.StrUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import jodd.util.StringUtil;
import lombok.RequiredArgsConstructor;

/**
 * Redis分布式锁切面服务
 */
@Aspect
@Component
@RequiredArgsConstructor
@Order(2)
public class RedisPeriodLockAspect {

    private static final String REDISSON_LOCK_PREFIX = "REDISSON_LOCK:";

    private final RedissonClient redissonClient;

    @Resource(name = IPropertyService.SYS_DICT_BEAN)
    private final IPropertyService<SysDictKeyEnum> propertyService;

    @Before("@annotation(periodLock)")
    public void beforePeriod(JoinPoint joinPoint, RedisPeriodLock periodLock) throws InterruptedException {
        String redisKey = getRedisKey(joinPoint, periodLock.lockName(), periodLock.key());
        RLock rLock = redissonClient.getLock(redisKey);
        // 判断是否已上锁
        if (rLock.isLocked()) {
            this.doLocked(periodLock, rLock);
        }
        // 获取过期时间
        long expireTime = periodLock.expire();
        try {
            String key = StrUtil.isNotBlank(periodLock.expireKeyEnum().getKey()) ?
                periodLock.expireKeyEnum().getKey() : periodLock.expireKey();
            String expireTimeStr = propertyService.getPropertyValue(
                SysDictModuleEnum.EXPIRE_TIME.getModule(), key, String.valueOf(periodLock.expire()));
            expireTime = Long.parseLong(expireTimeStr);
        } catch (NumberFormatException ignored) {}
        // 上锁
        if (!rLock.tryLock(-1, expireTime, periodLock.timeUnit())) {
            this.doLocked(periodLock, rLock);
        }
    }

    private void doLocked(RedisPeriodLock periodLock, RLock rLock) {
        long remainTimeToLive = rLock.remainTimeToLive();
        String defaultLockMsg = String.format("请求重置时间：%s秒", remainTimeToLive / 1000);
        String msgPrefix = StrUtil.isBlank(periodLock.lockMsg()) ? "" : periodLock.lockMsg() + ", ";
        String lockMsg = msgPrefix + defaultLockMsg;
        throw DefaultSaasException.build(lockMsg).status(HttpStatus.BAD_REQUEST);
    }

    /**
     * 将spel表达式转换为字符串
     *
     * @param joinPoint 切点
     * @return redisKey
     */
    private String getRedisKey(JoinPoint joinPoint, String lockName, String spel) {
        String spelValue = "";
        if (StringUtil.isNotBlank(spel)) {
            spelValue = SpringELUtils.getSpelValue(joinPoint, spel, String.class);
        }
        return REDISSON_LOCK_PREFIX + lockName + StrUtil.COLON + spelValue;
    }

}
